About the TblInfoDlg Component... The Delphi Component Writer's Guide recommends that you take frequently used dialogs and make them into components and that you create a 'wrapper' unit so that the form and it's associated resources are only loaded into memory when they're needed. They even provide an example with an 'About' dialog. Fine as far as it goes, but most dialogs are a little more complex. The TblInfoDlg component is a multi-page dialog that gives a user lots of information about a Paradox, DBase or ODBC table. This example (of sorts) consists of : The files for the wrapper unit: TBLINFO.PAS TBLINFO.DCU TBLINFO.DCR The files for the TblInfoDlg dialog: TBLDLG.PAS TDLIDLG.DCU TBLDLG.DFM The files for the sample program: DBIT.DPR DBIT.OPT DBIT.RES DBITEST.DCU DBITEST.DFM DBITEST.PAS To use all this, you have to install the TblInfoDlg component by adding tblinfo.pas to the component palette, where it will show up on the Data Controls page. Once this is done, the Dbit.dpr project file can be opened and run. What you'll see is a small form with four buttons-one pops up a File Open dialog that allows you to open a table, and the second one pops up the TblInfoDlg dialog with information about the aforementioned table, the third one closes the table and the fourth one closes the sample program. We at SJHDesign have found this component quite handy, it's been pretty well tested and is currently hard at work in real world applications at two client sites, so the code is(to our knowledge) solid. Using the TblInfoDlg component in your applications: _________________________________________________ All you have to do to use this component is supply it with a TTable component. This can be done at design time, by using the Object Inspector to put the fully qualified filename of a DBase, Paradox or ODBC table(which must already be opened before you call the TblInfoDlg object) in the TABLE property field or at run time with code similar to this in your application: Table1 : TTable; TblInfoDlg1 : TTblInfoDlg; Table1.open; {Note that the table MUST be open before calling the TblInfoDlg object} TblInfoDlg1.table := table1; TblInfoDlg1.execute; If the TTable object you pass to TblInfoDlg is invalid or inactive, an error message pops up and it cleans up and terminates. Additional properties allow easy runtime access to the dialog's font, color and caption. Any change made in the font or color is also made for all of the dialog's subcomponents. Why put form properties in a wrapper unit? __________________________________________________________ Good question. Delphi's architecture makes it very easy for the component user to change the properties of the 'wrapped' form (in this case, Tbldlg.pas)visually in the Object Inspector. That's great for design time, but some properties are useful to have around at run time. Since the TblDlg form is owned by the TblInfo unit, the user can access it's properties at runtime, but it would take some classically convoluted OOP code to do it. For example, by placing all the code needed to change the fonts of TblDlg and it's subcomponents into the wrapper unit, it takes the component user one line of code to do what otherwise would have taken 20 or 30 lines(this is also the reasoning behind the Parentxxx properties in Borland's components). In general, it helps to know how to add properties to a given component-non-visual components like TblInfoDlg(descended from TComponent) don't inherit very much in the way of properties. This listing shows all the code needed to let the user set TblDlg's font through the wrapper: interface type private fFont: TFont; procedure DlgFont(value : TFont); published property Font : TFont read FFont write DlgFont; {you don't specify a default value here-the program will use either the font defined in object inspector or the system default font} var MultPageDlg: TMultPageDlg;{the TblInfoDlg dialog form} implementation constructor TTblInfoDlg.Create(AOwner: TComponent); begin inherited Create(AOwner); fFont := TFont.Create; {the font and all other added property fields are initialized to default values} function TTblInfoDlg.Execute : Boolean; begin MultPageDlg := TMultPageDlg.Create(Application); MultPageDlg.Font := fFont; {this initializies the TblInfoDlg dialog and sets it's font to the default value} procedure tTblInfoDlg.DlgFont(value : TFont); begin if FFont <> Value then begin FFont.Assign(Value); {here is where the wrapper unit changes the value in the font property field when the user changes the value in the object inspector} Note that when you install the component, it defaults to whatever your system font is. Everybody's tastes are different, but I think it looks best using MsSanSerif, 8, Bold. You can adjust this in TTblInfoDlg's Font property at design time. The pattern is pretty much the same for the other properties depending on the property's data type. Why use BDE in Delpi Applications? ___________________________________ With the exception of a few omissions , Delphi's implementation of BDE will serve you pretty well. If however, you are developing anything out of the ordinary, you'll eventually have to use BDE functions to fill in the gaps in Delphi's simplified(but very neatly encapsulated) version of BDE. To use BDE directly, you have to include DBIPROCS and DBITYPES in your USES declaration. Then there's the size issue-Delphi programs can get huge(I shouldn't talk..using the TblInfoDlg component will add about 80k to a program, big even by Delphi standards). Judicious use of BDE calls instead of adding and creating more components can substantially cut down on the size, load time and memory overhead of your exe's. Dbbrowse(in delphi\demos\db\tools) is the only Delphi demo program with direct calls to BDE. Borland takes the approach of creating 4 components to handle the 4 BDE calls used in this program. While this approach works fine, it's not mandatory (the advantages to this approach are simplicity and the fact that Delphi handles most exceptions). Calling BDE functions from your program instead of componentizing them is a little harder, but could result in significant gains in speed and smaller exe size. With good error checking, any BDE function will work safely in a Delphi program, and a number of them are used in the procedures GetTableDate_Size, DisplayFields and Display Indexes. With the exception of GetTableDate_Size, the same information could have been gathered from Delphi's component methods and data structures, but the required overhead would be much greater considering that this is just a dialog that displays the stuff and disappears. In the case of GetTableDate_Size, the choice is between using a specialized BDE function like dbiOpenTableList(which walks the BDE handle chain to get physical file information that was stored at the time the file was opened) or using the runtime library routines FileSize and FileGetDate(which close and reset the file and could wreak all sorts of havoc on the Delphi data controls that are linked to the table). The former just seems simpler and safer. BDE Error Checking ___________________ Anyone who has had the experience of watching their Database program set off a long chain of excepetions and crash has probably learned this the hard way and can skip this part. Those who are new to BDE, though, might take some well meaning advice. Exception handling is an area where Delphi truly shines, making the Visual Basics and PowerBuilders of the world look feeble by comparison. When you use a Data Component, you get the benefit of Delphi's strong exception and error handling, but if you use direct BDE calls, you have to provide your own. Let's look at Dbbrowse again-the bdetable.pas unit(which contains the 3 BDE calls) wraps the calls in a CHECK routine which uses BDE's error stack to return the success or failure of direct BDE calls. While this is adequate for debugging and development, all you're doing here is getting information on whether your call went ok or why it failed. If the call DID fail, CHECK returns the error value and the program merrily goes on to the next call. If for example, the failed BDE call was supposed to return a cursor handle (hdbicur), the subsequent calls which access the data fields pointed to by this cursor will return a nil pointer(if you're lucky) or fail. To see this in action, load the Dbbrowse project(you have to install the three components in bdetable.pas first) and use the IDE Run command to run the Dbbrowse program. If you choose the View Table command without having the outline cursor on a table, you'll see a whole chain of error messages and then a GP fault as the BDE functions repeatedly try to access data in a nonexistent table. What we're trying to say here is that there are many cases whre your function that uses BDE calls must clean up and bail out if the call wasn't successful. Calls which return a handle of any type (like DbiOpenTableList, DbiOpenIndexList, or DbiOpenFieldList) fall into this category. Calls which return pointers to BDE data structures(like dbiGetCursorProps) can also be dangerous if execution continues after they fail. Table and field level calls like DBIGetNext Record or DBIGetField will simply return nil pointers or empty buffers if they fail- this alone won't cause your program to crash, but it won't endear you to the users of your program when they see garbage or blank fields instead of data. This is the reason for the paranoid-type code in DisplayIndexes, DisplayFields and GetTableDate_Size. To use BDE's error stack, you need to include DBIERRS in your uses clause. All BDE functions return DBIERR_NONE if successful and the three functions just mentioned show how to utilize this return value. Here's an example: var hCur : hDBICur;{BDE Cursor handle} name : array[0..80]of Char;{names passed to BDE functions can't be strings} recbuf :pbyte;{record buffer} tblprops :curprops;{table properties struct} begin recs := table.FieldCount-1; strPcopy(name,Table.TableName); {get a cursor handle to the field list table} if DbiOpenFieldList(table.DBHandle, name, nil, False, hCur) = DBIERR_NONE then {call was successful-continue} begin if dbiGetCursorProps(hCur,tblprops)= DBIERR_NONE then begin {call was successful-continue} dbiSetToBegin(hCur); for i := 0 to recs do begin {$I+} {use Delphi's exception handling routine to handle failed allocations gracefully} try GetMem(RecBuf, tblProps.iRecBufSize*sizeof(BYTE)); except on EOutOfMemory do {bail out and clean up} begin DbiCloseCursor(hCur); {show error message here if you want} exit; end; {$I-} end; {various record and field level calls here} if recbuf <> nil then freemem(recbuf, tblprops.iRecBufSize * sizeof(BYTE)); end; DbiCloseCursor(hCur); end else {call to dbiGetCursorProps failed-bail out and clean up} begin DbiCloseCursor(hCur); {show error message} end; end else ;{call to DbiOpenFieldList failed-show error message here} Borland's advice that you get the BDE devleloper's guide if you want to do this kind of stuff is very well taken. Contact them for info if you want this- I've found the Borland people on their various CIS development forums to be quite helpful. Anyway, I've said enough-you're better off looking at the code. Feel free to use this component in your programs or do whatever you like with it. Comments, improvements, opposing points of view, questions and the like are always welcome. Scott Hanrahan SJHDesign, INC CIS 70144,3033 PS : The lawyer said I should always put in a disclaimer, even for sample code uploaded for the edification of others. This is example code for instructional purposes only and we do not make any warranties as to it's quality or suitability for your purposes-that's up to you to determine. Enjoy!